/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 */

import { CacheStoreManager, DurationEvent, iconv, isWindows, MetricLogType, SubmitOption, TaskDetails, TaskState } from '@ohos/hvigor';
import { getOsLanguage } from '@ohos/hvigor-common';
import { ErrorUtil, HvigorOhosPluginAdaptor } from '@ohos/hvigor-logger';
import { spawn } from 'child_process';
import { createWriteStream } from 'fs';
import { readFile } from 'node:fs/promises';
import os from 'os';
import path from 'path';

import { ExternalNativeOpt } from '../options/build/build-opt.js';
import { OhosLogger } from '../utils/log/ohos-logger.js';

import { TargetTaskService } from './service/target-task-service.js';
import { OhosHapTask } from './task/ohos-hap-task.js';

const logger: OhosLogger = OhosLogger.getLogger('Native');

type NativeExecutionArgs = {
  commandLine: string[];
  logFolder: string;
};

const ErrorFlags: Map<string, string> = new Map([
  ['error: undefined symbol: ', 'A_UNDEFINED_SYMBOL_ERROR_HAS_OCCURRED'],
  ['error: unknown type name ', 'A_UNKNOWN_TYPE_NAME_ERROR_HAS_OCCURRED'],
]);

/**
 * BuildNativeWithNinja和BuildNativeWithCmake的抽象父类
 *
 * @since 2022/8/23
 */
export abstract class AbstractBuildNative extends OhosHapTask {
  protected readonly _nativeOption?: ExternalNativeOpt;

  protected constructor(targetService: TargetTaskService, taskDetails: TaskDetails) {
    super(targetService, taskDetails);
    this._nativeOption = targetService.getBuildOption().externalNativeOptions;
  }

  /**
   * 执行编译命令
   *
   * @param commandLine 命令行
   * @param dir
   * @param callback 回调函数
   * @param callbackInput 回调函数的输入
   * @param subDurationEvent 子事件
   */
  protected async executeCommand(
    commandLine: string[],
    dir: string,
    callback: Function,
    callbackInput: unknown[],
    subDurationEvent: DurationEvent,
  ): Promise<void> {
    const inputData: NativeExecutionArgs = {
      commandLine: commandLine,
      logFolder: dir,
    };
    const submitOption: SubmitOption = {
      workInput: inputData,
      callback: callback,
      callbackInput: callbackInput,
    };
    const workPath: string = path.resolve(__filename, 'nativeExecution');
    if (this.getWorkerPool().submit(this, workPath, submitOption).getState() === TaskState.REJECT) {
      const executeTaskName = 'execute the command';
      const subToolDurationEvent = subDurationEvent.createSubEvent(executeTaskName, '');
      subToolDurationEvent.start();
      await nativeExecution(inputData);
      await callback(...callbackInput);
      subToolDurationEvent.stop();
      subToolDurationEvent.setLog(executeTaskName, MetricLogType.INFO);
    }
  }
}

export const nativeExecution = async (args: NativeExecutionArgs, workerCacheManager?: CacheStoreManager) => {
  workerCacheManager?.mount('nativeExecution').setCache('args', args);
  const charset = isWindows() ? 'GBK' : 'UTF-8';
  const logFile = path.resolve(args.logFolder, 'output.log');
  const cp = spawn(args.commandLine[0], args.commandLine.slice(1), { windowsHide: true });
  const output = createWriteStream(logFile, {});
  let outLine: string[] = [];
  let errLine: string[] = [];
  cp.stdout?.on('data', (data) => {
    const message = iconv.decode(<Buffer>data, charset);
    output.write(message);
    outLine.push(message);
    if (message.endsWith('\r') || message.endsWith('\n')) {
      logger.debug(outLine.join(''));
      outLine = [];
    }
  });
  cp.stderr?.on('data', (data) => {
    const message = iconv.decode(<Buffer>data, charset);
    output.write(message);
    errLine.push(message);
    if (message.endsWith('\r') || message.endsWith('\n')) {
      logger.warn(errLine.join(''));
      errLine = [];
    }
  });
  return new Promise<void>((resolve, reject) => {
    cp.once('close', async (code) => {
      output.end();
      if (code === 0) {
        resolve();
      } else {
        const message = await readException(logFile);
        logger._buildError(message)._printErrorAndExit();
        reject(message);
      }
    });
  });
};

const readException = async (logFile: string) => {
  let message;
  try {
    const text = await readFile(logFile, { encoding: 'utf8' });
    message = `Exceptions happened while executing: ${text}`;
    for (const [flag, id] of ErrorFlags.entries()) {
      if (text.includes(flag)) {
        const logMsg = ErrorUtil.getFirstErrorAdaptorMessage(new HvigorOhosPluginAdaptor(id).getErrorMessage());
        let solutionMessage = `${logMsg.message}${os.EOL}* Try the following:${os.EOL}`;
        solutionMessage += `  > ${logMsg.solutions?.join(' ')}\n`;
        if (logMsg.moreInfo) {
          solutionMessage += `  > More info: ${logMsg.moreInfo[getOsLanguage()]}`;
        }
        message += solutionMessage;
        break;
      }
    }
  } catch (e) {
    message = `Log parsing failed. Please check the log file at ${logFile}.`;
  }
  return message;
};
